/* Copyright (c) [2025] [zhi1208@126.com]
 * SPDX-License-Identifier: MIT
 */

/*********************
 *      INCLUDES
 *********************/
#include "FreeRTOS.h"
#include "semphr.h"
#include "bsp_ws2812.h"
#include "tim.h"

/*********************
 *      DEFINES
 *********************/
#define WS2812_BUFFER_SIZE (WS2812_NUM_LEDS * 24)
#define WS2812_RESET_PULSES 50

/**********************
 *      TYPEDEFS
 **********************/
typedef struct
{
    uint8_t r;
    uint8_t g;
    uint8_t b;
} rgb_color_t;

typedef struct
{
    uint16_t hue;
    uint8_t saturation;
    uint8_t brightness;
} hsv_color;

/**********************
 *  STATIC PROTOTYPES
 **********************/
static void hsv_to_rgb(hsv_color hsv, rgb_color_t *rgb);
static void set_led_color(uint8_t index, rgb_color_t color);
static void encode_byte(uint8_t data, uint16_t start_index);

/**********************
 *  STATIC VARIABLES
 **********************/
static uint16_t ws2812_buffer[WS2812_BUFFER_SIZE + WS2812_RESET_PULSES];
static hsv_color led_hsv[WS2812_NUM_LEDS];

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/
SemaphoreHandle_t ws2812_semaphore;

void bsp_ws2812_init()
{
    ws2812_semaphore = xSemaphoreCreateBinary();
    vQueueAddToRegistry(ws2812_semaphore, "ws2812_semaphore");
    xSemaphoreGive(ws2812_semaphore);
}

void bsp_ws2812_set_hue(uint8_t index, uint16_t hue)
{
    if (index >= WS2812_NUM_LEDS)
        return;

    led_hsv[index].hue = hue % 361;
}

void bsp_ws2812_set_saturation(uint8_t index, uint8_t saturation)
{
    if (index >= WS2812_NUM_LEDS)
        return;

    if (saturation > 100)
        saturation = 100;
    led_hsv[index].saturation = saturation;
}

void bsp_ws2812_set_brightness(uint8_t index, uint8_t brightness)
{
    if (index >= WS2812_NUM_LEDS)
        return;

    if (brightness > 100)
        brightness = 100;
    led_hsv[index].brightness = brightness;
}

void bsp_ws2812_set_all_hue(uint16_t hue)
{
    hue = hue % 361;
    for (int i = 0; i < WS2812_NUM_LEDS; i++)
        led_hsv[i].hue = hue;
}

void bsp_ws2812_set_all_saturation(uint8_t saturation)
{
    if (saturation > 100)
        saturation = 100;
    for (int i = 0; i < WS2812_NUM_LEDS; i++)
        led_hsv[i].saturation = saturation;
}

void bsp_ws2812_set_all_brightness(uint8_t brightness)
{
    if (brightness > 100)
        brightness = 100;
    for (int i = 0; i < WS2812_NUM_LEDS; i++)
        led_hsv[i].brightness = brightness;
}

void bsp_ws2812_update(void)
{
    xSemaphoreTake(ws2812_semaphore, portMAX_DELAY);

    for (int i = 0; i < WS2812_NUM_LEDS; i++)
    {
        rgb_color_t rgb;
        hsv_to_rgb(led_hsv[i], &rgb);
        set_led_color(i, rgb);
    }

    for (int i = 0; i < WS2812_RESET_PULSES; i++)
        ws2812_buffer[WS2812_BUFFER_SIZE + i] = 0;

    HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_1, (uint32_t *)ws2812_buffer,
                          WS2812_BUFFER_SIZE + WS2812_RESET_PULSES);
}

void bsp_ws2812_clear(void)
{
    xSemaphoreTake(ws2812_semaphore, portMAX_DELAY);

    hsv_color hsv = {0, 0, 0};
    for (int i = 0; i < WS2812_NUM_LEDS; i++)
    {
        rgb_color_t rgb;
        hsv_to_rgb(hsv, &rgb);
        set_led_color(i, rgb);
    }

    for (int i = 0; i < WS2812_RESET_PULSES; i++)
        ws2812_buffer[WS2812_BUFFER_SIZE + i] = 0;

    HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_1, (uint32_t *)ws2812_buffer,
                          WS2812_BUFFER_SIZE + WS2812_RESET_PULSES);
}

/**********************
 *   STATIC FUNCTIONS
 **********************/
static void hsv_to_rgb(hsv_color hsv, rgb_color_t *rgb)
{
    float h = (float)hsv.hue / 360.0f;
    float s = (float)hsv.saturation / 100.0f;
    float v = (float)hsv.brightness / 100.0f;

    float r, g, b;
    r = g = b = 0.0;
    int i = (int)(h * 6);
    float f = h * 6 - i;
    float p = v * (1 - s);
    float q = v * (1 - f * s);
    float t = v * (1 - (1 - f) * s);

    switch (i % 6)
    {
    case 0:
        r = v;
        g = t;
        b = p;
        break;
    case 1:
        r = q;
        g = v;
        b = p;
        break;
    case 2:
        r = p;
        g = v;
        b = t;
        break;
    case 3:
        r = p;
        g = q;
        b = v;
        break;
    case 4:
        r = t;
        g = p;
        b = v;
        break;
    case 5:
        r = v;
        g = p;
        b = q;
        break;
    }

    rgb->r = (uint8_t)(r * 255);
    rgb->g = (uint8_t)(g * 255);
    rgb->b = (uint8_t)(b * 255);
}

static void encode_byte(uint8_t data, uint16_t start_index)
{
    for (int i = 0; i < 8; i++)
    {
        if (data & (1 << (7 - i)))
            ws2812_buffer[start_index + i] = 134;
        else
            ws2812_buffer[start_index + i] = 67;
    }
}

static void set_led_color(uint8_t index, rgb_color_t color)
{
    if (index >= WS2812_NUM_LEDS)
        return;

    uint16_t start_index = index * 24;

    encode_byte(color.g, start_index);
    encode_byte(color.r, start_index + 8);
    encode_byte(color.b, start_index + 16);
}
